// Copyright (C) Calum Grant 2008

#include <cppscript>
#include <dynamic/extensions.hpp>
#include <sstream>

#include "unit_tests.hpp"


namespace
{
	var str_clear(var self)
	{
		self["object"].as<std::string>().clear();
		return self;
	}

	var str_get(var self)
	{
		return self["object"].as<std::string>();
	}

	var str_create(var str)
	{
		return object().extend
			("object", create<std::string>(str.as_string()))
			("get", str_get)
			("clear", str_clear);
	}
}


class my_var_impl : public var_impl
{
public:
	void copy_to(void*p) const { new(p) my_var_impl(*this); }

	std::string class_name() { return "my_var_impl"; }

	int as_int() { return 5; }
};


class my_shared_var_impl : public shared_var_impl
{
public:
	std::string class_name() { return "my_shared_var_impl"; }

	int as_int() { return 6; }

	void mark_children(gc::garbage_collector&) { }
};


void test_native()
{
	// Construction
	var str = create<std::string>(std::string("Boo"));
	var empty_str = create<std::string>();

	// Basics
	assert( str.impl().shared_var() );
	assert( str.class_name() == "native" );
	assert( str != null );
	assert( str == str );
	assert( str == +str );
	assert_throws( not_supported, str() );
	assert_throws( not_supported, pickle(str) );
	assert( str.impl().comparison_index() == cmp_native );
	assert( str > bind( str_clear, var() ) );
	assert( str.max_args() == -1 );

	// Conversions
	// The C++ object can be retrieved using the as<> member template.
	assert( str.as<std::string>() == "Boo" );

	assert( !str.as_string().empty() );
	assert( !str.as_wstring().empty() );

	assert( !str );
	assert( str.as_int() == 0 );
	assert( str.as_double() == 0.0 );

	// Retrieving the wrong type throws an exception
	assert_throws( not_supported, str.as<int>() );

	test_default_container( str );
	test_default_iterator( str );
	test_default_assignment( str );

	// Test operators (they all throw)
	test_default_operators( str );

	/// Wrapping a C++ class with a script object
	var dumb_str = str_create("Foo");
	assert( dumb_str["get"]() == "Foo" );
	dumb_str["clear"]();
	assert( dumb_str["get"]() == "" );

	// Cloning does not clone the C++ object
	// (because some objects don't have a working copy constructor)
	var str2 = +str;
	str.as<std::string>() = "shoe";
	assert( str2.as<std::string>() == "shoe" );

	// Tests variant() function.
	assertx( create_impl(my_var_impl()).as_int() == 5 );

	// Test custom shared var impl.
	assertx( var(new my_shared_var_impl()).as_int() == 6 );
}


void test_stream()
{
	std::ostringstream ss;
	ss << var("fu") << var(2);
	assert( ss.str() == "fu2" );
}


void test_gc()
{
	{
		var v1 = array();
		v1[0] = v1;
	}

	gc_force_collect();
	int init_object_count = gc::global_gc().object_count();
	gc_force_collect();
	assert(gc::global_gc().object_count() == init_object_count);

	{
		var o = array();
		for(int i=0; i<10000; ++i)
		{
			o[i] = i+1;
		}
	}

	gc_force_collect();
	assert(gc::global_gc().object_count() == init_object_count);

	// Test disable gc:
	{
		gc_disable();
		finally( gc_enable );

		init_object_count = gc::global_gc().object_count();
		{
			var o = array();
			for(int i=0; i<10000; ++i)
			{
				o[i] = i+1;
			}
		}
		int count = gc::global_gc().object_count();
		gc_hint_collect();	// No effect
		assert( gc::global_gc().object_count() == count );
	}

}


void test_global()
{
	// There is a variable called cg::global which holds global variables
	// Whilst global state is ugly, it can hold functions, types and static data
	var g = global();

	// Global is an object, so you can add attributes to it:
	global()["hw"] = 12;
	global()["x"] = object();
	global()["x"]["y"] = "fu";

	assert( global()["hw"] == 12 );
	assert( global()["x"]["y"].as_string() == "fu" );
}
